package com.panopset.gui;

import static com.panopset.Strings.isPopulated;
import static com.panopset.Util.log;
import static com.panopset.Util.x;

import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;
import java.util.Map.Entry;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;

import com.panopset.Strings;
import com.panopset.centralen.KeySupplier;
import com.panopset.centralen.NamedValue;
import com.panopset.gui.container.PanFile;
import com.panopset.gui.container.TitledPanel;
import com.panopset.io.FileProcessor;
import com.panopset.io.LockedWriter;

/**
 *
 * Edit property values.
 *
 * @author Karl Dinwiddie
 *
 */
public final class PropvalEditor {

    /**
     * Validating document.
     */
    private AbstractDocument doc;

    /**
     * Number of test keys is 10.
     */
    private static final int NUM_KEYS = 2;

    /**
     * Test PropvalEditor.
     *
     * @param s
     *            Not used.
     */
    public static void main(final String... s) {
        JFrame f = com.panopset.gui.UtilGUI.createStandardFrame();
        PropvalEditor pve = new PropvalEditor();
        pve.setDocument(new NumericDocument());
        pve.getPropsFile().setValue(
                Strings.USER_HOME
                + Strings.FILE_SEP
                + "temp"
                + Strings.FILE_SEP
                + "zonk.txt");
        pve.setKeySupplier(new KeySupplier() {
            @Override
            public Vector<NamedValue> getKeys() {
                Vector<NamedValue> s = new Vector<NamedValue>();
                for (int i = 0; i < NUM_KEYS; i++) {
                    StringBuffer lbl = new StringBuffer();
                    lbl.append("y" + i);
                    s.add(new NamedValue(lbl.toString(), "k" + i));
                }
                return s;
            }
        });
        f.add(pve.getPanel());
        f.setVisible(true);
    }

    /**
     * Set validating document.
     *
     * @param document Validating document.
     */
    public void setDocument(final AbstractDocument document) {
        doc = document;
        softRefresh();
    }

    /**
     * Save to properties file.
     */
    private void save() {
        LockedWriter ow = new LockedWriter(getPropsFile().getFile());
        for (Entry<String, DataEntry> entry : dataEntries.entrySet()) {
            StringWriter sw = new StringWriter();
            sw.append(entry.getKey());
            sw.append("=");
            sw.append(entry.getValue().getValue());
            ow.writeln(sw.toString());
        }
        ow.writeln("");
        ow.close();
    }


    /**
     * Load from properties file.
     */
    private void load() {
        File f = getPropsFile().getFile();
        if (f == null) {
            return;
        }
        if (!f.exists()) {
            return;
        }
        new FileProcessor(getPropsFile().getFile(),
                new FileProcessor.Listener() {

                    @Override
                    public void process(final FileProcessor processor,
                            final String[] s) {
                        PropvalEditor.this.updateValueFromFileLine(s[0]);
                    }

                    @Override
                    public void finish(final FileProcessor processor) {
                        // Nothing to do here.
                    }
                }).exec();
        hardRefresh();
    }

    /**
     * Update value from file line.
     * @param s Raw value.
     */
    void updateValueFromFileLine(final String s) {
        StringTokenizer st = new StringTokenizer(s, "=");
        if (st.countTokens() < 2) {
            return;
        }
        String key = st.nextToken();
        StringWriter vb = new StringWriter();
        vb.append(st.nextToken());
        while (st.hasMoreElements()) {
            vb.append("=");
            vb.append(st.nextToken());
        }
        String val = vb.toString();
        update(key, val);
    }

    /**
     * Update.
     * @param key Key.
     * @param val Value.
     */
    void update(final String key, final String val) {
        FOR: for (Entry<String, DataEntry> entry : dataEntries.entrySet()) {
            if (entry.getKey().equals(key)) {
                entry.getValue().setValue(val);
                break FOR;
            }
        }
    }

    /**
     * Data entries.
     */
    private final Map<String, DataEntry> dataEntries =
        Collections.synchronizedSortedMap(new TreeMap<String, DataEntry>());

    /**
     * Get the data entry object for a given named key.  It will be created
     * if it does not already exist.
     * @param key Key.
     * @return Data entry.
     */
    private DataEntry getDataEntry(final NamedValue key) {
        DataEntry de = dataEntries.get(key.getValue());
        if (de == null) {
            de = new DataEntry(key);
            dataEntries.put(key.getValue(), de);
        }
        return de;
    }

    /**
     * Hard refresh.
     */
    private void hardRefresh() {
        getPropsPanel().removeAll();
        hardRefreshAssemble();
        getPropsPanel().invalidate();
        UtilGUI.updateComponent(getPropsPanel());
    }

    /**
     * Add keys.
     */
    private void hardRefreshAssemble() {
        if (keysplr == null) {
            return;
        }
        GridBagConstraints gbc1 = new GridBagConstraints();
        GridBagConstraints gbc2 = new GridBagConstraints();
        gbc1.gridx = 1;
        gbc1.gridy = 0;
        gbc1.anchor = GridBagConstraints.EAST;
        gbc2.gridx = 2;
        gbc2.gridy = 0;
        gbc2.weightx = 1.0;
        gbc2.fill = GridBagConstraints.HORIZONTAL;
        for (NamedValue nv : keysplr.getKeys()) {
            DataEntry de = getDataEntry(nv);
            getPropsPanel().add(new JLabel("   " + nv.getName()), gbc1);
            getPropsPanel().add(de.getTitledPanel().getPanel(), gbc2);
            gbc1.gridy++;
            gbc2.gridy++;
        }
        softRefresh();
    }

    /**
     * Soft refresh.
     */
    private void softRefresh() {
        for (Entry<String, DataEntry> entry : dataEntries.entrySet()) {
            entry.getValue().setDocument(doc);
            entry.getValue().update();
        }
    }

    /**
     * Set key supplier.
     *
     * @param keySupplier
     *            Key supplier.
     */
    public void setKeySupplier(final KeySupplier keySupplier) {
        keysplr = keySupplier;
        hardRefresh();
    }

    /**
     * @return Panel.
     */
    public TPanel getPanel() {
        if (panel == null) {
            panel = new TPanel();
            panel.setLayout(new BorderLayout());
            panel.add(BorderLayout.SOUTH, getPropsFile().getPanel());
            panel.add(BorderLayout.CENTER, new JScrollPane(getPropsPanel()));
            load();
        }
        return panel;
    }

    /**
     * Key supplier.
     */
    private KeySupplier keysplr;

    /**
     * Props Panel.
     */
    private TPanel propsPanel;

    /**
     * @return Props panel.
     */
    private TPanel getPropsPanel() {
        if (propsPanel == null) {
            propsPanel = new TPanel(new GridBagLayout());
        }
        return propsPanel;
    }

    /**
     * Panel.
     */
    private TPanel panel;

    /**
     * Props file.
     */
    private PanFile propsFile;

    /**
     * @return Properties file.
     */
    public PanFile getPropsFile() {
        if (propsFile == null) {
            propsFile = new PanFile(x("File"), x("Disk file") + ".");
        }
        return propsFile;
    }

    /**
     * Data entry.
     */
    private class DataEntry {

        /**
         * Text field.
         */
        private final JTextField tf = new JTextField();

        /**
         * @return Text value.
         */
        public String getValue() {
            return tf.getText();
        }

        /**
         * @param document Validating document.
         */
        void setDocument(final AbstractDocument document) {
            if (document == null) {
                return;
            }
            if (tf.getDocument() == null
                    || !document.getClass().getCanonicalName()
                    .equals(tf.getDocument().getClass().getCanonicalName())) {
                try {
                    tf.setDocument((AbstractDocument)
                    Class.forName(document.getClass().getCanonicalName())
                    .newInstance());
                } catch (Exception e) {
                    log(e);
                }
            }
        }

        /**
         * Set value.
         * @param val Value to set.
         */
        public void setValue(final String val) {
            tf.setText(val);
            update();
        }

        /**
         * Named value.
         */
        private final NamedValue nv;

        /**
         * Panel.
         */
        private final TitledPanel p;

        /**
         * @return Titled panel.
         */
        public TitledPanel getTitledPanel() {
            return p;
        }

        /**
         * @param namedValue Named value.
         */
        DataEntry(final NamedValue namedValue) {
            nv = namedValue;
            p = new TitledPanel(nv.getValue(), new GridBagLayout(), nv
                    .getName());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.anchor = GridBagConstraints.WEST;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            gbc.weightx = 1.0;
            p.getPanel().add(tf, gbc);
            tf.setToolTipText(nv.getName());
            tf.addKeyListener(new KeyAdapter() {
                @Override
                public void keyReleased(final KeyEvent e) {
                    DataEntry.this.update();
                }
            });
            tf.addFocusListener(new FocusAdapter() {
                @Override
                public void focusLost(final FocusEvent e) {
                    PropvalEditor.this.save();
                }
            });
        }

        /**
         * Update.
         */
        protected void update() {
            if (isPopulated(getValue())) {
                getTitledPanel().setNormalHighlighting();
            } else {
                getTitledPanel().setAlertHighlighting();
            }
            UtilGUI.updateComponent(getTitledPanel().getPanel());
        }
    }
}
